/* Copyright © 2023 - 2024 Coremail论客
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


export type EmailAddress = {
  name: string;
  email: string;
};

/*
 * 一个支持流式读取的buffer，用于存储邮件正文、附件、内联附件等的内容
 * 默认实现是使用内存，对于大文件，会有内存占用问题。
 * 一般的实现是一个文件，但是也可以是一个数据库表，这个需要根据具体的情况来实现
 * 通过MailEngine的bufferCreator参数，可以使用自定义的IBuffer对象
 * 支持一边读取一边写入，支持随机读取
 */
export interface IBuffer {
  feed(data: string | Uint8Array): Promise<void>;
  end(data?: string | Uint8Array): Promise<void>;
  read(): AsyncIterableIterator<string>;
  readAll(): Promise<string>;
  getSize(): Promise<number>;

  readRaw(): AsyncIterableIterator<Uint8Array>;
  readAllRaw(): Promise<Uint8Array>;
  getSizeRaw(): Promise<number>;

  append(data: IBuffer): Promise<void>;
}

export type BufferHint = {
  // 存放eml原文
  isEml?: boolean;
  // 存放附件
  isAttachment?: boolean;
  // 存放内联附件
  isInlineImage?: boolean;
  // 存放邮件正文
  isBody?: boolean;
  // 大小
  size?: number;
  // 使用文件存储
  useFile?: boolean;
  // Mime对象
  mime?: Mime;

  [key: string]: string | number | boolean | Mime;
};

export interface IBufferCreator {
  createBuffer(hint?: BufferHint): IBuffer;
}

export type InlineImage = {
  name: string;
  body: IBuffer;
  contentId: string;
  contentType: string;
  type: string;
  subType: string;
  size: number;
};

export type Attachment = {
  name: string;
  body: IBuffer;
  contentType: string;
  type: string;
  subType: string;
  size: number;
};

export enum MailPriority {
  HIGH = 1,
  NORMAL = 3,
  LOW = 5,
};

export interface IMail {
  getId(): string;

  getFrom(): Promise<EmailAddress>;
  getTo(): Promise<EmailAddress[]>;
  getCc(): Promise<EmailAddress[]>;
  getBcc(): Promise<EmailAddress[]>;
  getSubject(): Promise<string>;
  getDate(): Promise<Date>;
  getSize(): Promise<number>;

  /*
   * 获取邮件原文，即MIME格式的字符串
   */
  getRaw(): Promise<IBuffer>;

  /*
   * 获取邮件的优先级
   * 优先级根据邮件头的X-Priority或者X-MSMail-Priority来确定
   * 如果没有设置则默认为 NORMAL
   */
  getPriority(): Promise<MailPriority>;

  /*
   * 获取邮件的Message-ID头的信息
   */
  getMessageId(): Promise<string | undefined>;

  /*
   * 获取一个邮件头信息。如果邮件头中有多个，则会用\r\n连接
   * @headerName: 邮件头的名字，忽略大小写
   */
  getHeader(headerName: string): Promise<string | undefined>;
  /*
   * 获取全部邮件头信息。
   * @CAVEAT: 返回的是内部数据的对象，不要修改它。
   */
  getAllHeaders(): Promise<MimeHeader>;

  /*
   * 判断是否有附件
   */
  hasAttachment(): Promise<boolean>;

  /*
   * 获取内联图片列表
   */
  getInlines(): Promise<MailAttachment[]>;
  /*
   * 获取单个内联图片
   */
  getInline(index: number): Promise<MailAttachment>;

  /*
   * 获取附件列表
   */
  getAttachments(): Promise<MailAttachment[]>;
  /*
   * 获取单个附件
   */
  getAttachment(index: number): Promise<MailAttachment>;


  getHtml(): Promise<string>;
  getPlain(): Promise<string>;
  getDigest(num: number): Promise<string>;

  isSeen(): Promise<boolean>;
  setSeen(isSeen: boolean): Promise<void>;

  isFlagged(): Promise<boolean>;
  setFlagged(isFlagged: boolean): Promise<void>;

  isAnswered(): Promise<boolean>;
  setAnswered(isAnswered: boolean): Promise<void>;

  /*
   * 获取代发者，即邮件的Sender头信息
   */
  getDelegate(): Promise<EmailAddress | undefined>;

  getAttributes(): Promise<MailAttribute[]>;
  setAttributes(attrs: MailAttribute[]): Promise<void>;

  getFolder(): IFolder; // 所属文件夹

  on(event: "flagged-changed", handler: (hasAttr: boolean) => void): void;
  on(event: "seen-changed", handler: (hasAttr: boolean) => void): void;
  on(
    event: "attr-changed",
    handler: (attr: MailAttribute, hasAttr: boolean) => void
  ): void;

  delete(): Promise<void>;
  moveTo(folder: IFolder): Promise<string>;
}

export type Order = {
  by: "date" | "size"; // 按照日期、大小排序
  desc: boolean; // 是否降序
};

export interface IFolder {
  getName(): string;
  getFullName(): string;
  getType(): FolderType;
  // 获取邮件列表
  /*
   * 获取邮件列表
   * @param size: 获取邮件的数量
   * @param startMailId: 开始的邮件id，如果不指定，则从最新的邮件开始
   * @param order: 排序规则，如果不指定，则按照日期倒序
   */
  getMailList(
    size: number,
    startMailId?: string,
    order?: Order
  ): Promise<IMail[]>;

  isSelectable(): Promise<boolean>;

  getMailCount(): Promise<number>;
  getUnreadMailCount(): Promise<number>;
  refresh(): Promise<void>;
  getRecentCount(): Promise<number>;
  getSubFolders(): Promise<IFolder[]>;
  getSubFolder(name: string): Promise<IFolder>;
  createSubFolder(name: string): Promise<IFolder>;
  renameFolder(name: string): Promise<void>;
  deleteSubFolder(name: string): Promise<void>;

  getMail(mailId: string): Promise<IMail>;
  getMail(mailIndex: number, order?: Order): Promise<IMail>;
  getMailIndex(mailId: string, order?: Order): Promise<number>;

  addMail(mime: string | IBuffer, attrs?: MailAttribute[]): Promise<string>;

  open(): Promise<string>;
  close(): Promise<void>;
  isOpen(): boolean;

  on(event: "new-mails", handler: (mail: IMail[]) => void): void;
  on(event: "mail-deleted", handler: (mailId: string) => void): void;
}

export enum MailAttribute {
  Seen,
  Flagged,
  Answered,
  Deleted,
  Draft,
  Recent
}

export type MailEnvelope = {
  date: Date;
  subject: string;
  from: EmailAddress;
  // sender: EmailAddress[],
  // reply_to: EmailAddress[],
  to: EmailAddress[];
  cc: EmailAddress[];
  bcc: EmailAddress[];
  // in_reply_to: EmailAddress[],
  messageId: string;
}

export type MimeParams = {
  charset?: string;
  name?: string;
  filename?: string;
  boundary?: string;
  [key: string]: string | undefined;
};

export type MimeDispositionType = 'attachment' | 'inline';
export type MimeDisposition = {
  type: MimeDispositionType;
  params: MimeParams;
};

/*
 * 邮件结构，参考RFC 3501
 * https://tools.ietf.org/html/rfc3501#section-7.4.2
 * todo:
 * 本结构是根据RFC 3501定义的，还没有处理MESSAGE类型的数据
 */

export type TransferEncoding = '8bit' | 'base64' | 'quoted-printable';
export type MailPart = {
  contentType: ContentType;
  disposition?: MimeDisposition;
  encoding?: TransferEncoding;
  contentId?: string;
  partId: string;
  size: number;
}

export type MailStructure = MailPart & {
  children: MailStructure[];
}

export type MailAttachment = MailPart & {
  getFileName: () => string;
  /*
   * 获取附件内容，已经解码后的内容
   */
  getBody: () => Promise<IBuffer>;
}

export type Mime = MailPart & {
  headers: MimeHeader;
  body: IBuffer;
  children: Mime[];
}

export type MailItem = {
  date: Date;
  subject: string;
  from: EmailAddress;
  sender?: EmailAddress,
  replyTo: EmailAddress[],
  to: EmailAddress[];
  cc: EmailAddress[];
  bcc: EmailAddress[];
  // in_reply_to: EmailAddress[],
  messageId: string;
  extraHeader: MimeHeader;
  html: string;
  plain: string;
  attachments: MailAttachment[];
  inlines: MailAttachment[];
  priority?: MailPriority;
}

export type ContentType = {
  type: 'multipart' | 'text' | string;
  subType: 'mixed' | 'related' | 'alternative' | string;
  params: MimeParams;
};

export type MailId = number | string | [string, string];
export type MailData = {
  mailId: MailId;
  attributes: MailAttribute[];
  attachments: MailAttachment[];
  inlines: MailAttachment[];
  html: string;
  plain: string;
} & MailEnvelope;

export type MailDataAny = Partial<MailData>

export type MailBasic = {
  envelope: MailEnvelope;
  structure?: MailStructure;
  hasAttachment: boolean;
  attributes: MailAttribute[];
  id: string;
  // 邮件大小，单位是字节，由于MIME是纯ASCII文本，所以实际上也是字符数
  // 包括头部和正文
  size: number;
};

/*
 * 邮件头信息。邮件头是MIME格式的头信息。
 * 在MIME格式里面，一个头信息由两部分组成，name和value，中间用冒号隔开。
 * name不区分大小写，content-type和Content-Type，甚至conTent-tyPe都是一样的
 */
export interface MimeHeader {
  get(name: string): string | undefined;
  getList(name: string): string[] | undefined;
  set(name: string, value: string): MimeHeader;
  replace(name: string, value: string): MimeHeader;
  has(name: string): boolean;
  delete(name: string): boolean;
  entries(useRawName?: boolean): IterableIterator<[string, string]>;
  size: number;
}

export interface IEmlParser {
  /*
   * 解析邮件
   * @param mime: 邮件的MIME格式字符串
   * @return: 返回解析后的邮件内容，参考MailData的定义
   */
  mimeParse(
    content: string | IBuffer
  ): Promise<Mime>;
}

export enum FolderType {
  root = 0,
  inbox = 1,
  draft,
  sent,
  trash,
  spam,
  archive,
  virus,
  other,

  virtual, // 这个之后的是虚拟文件夹
  important,
}

export type FolderData = {
  name: string;
  fullName: string;
  type?: FolderType;
  parent?: string;
  mailCount?: number;
  unreadCount?: number;
  recent?: number;
  mode?: "rw" | "ro";
  status?: "open" | "close";
  uidValidity?: number;
  uidNext?: number;
  // 是否有NoSelect属性，不能select的文件夹也不能放邮件
  noSelect?: boolean;
};

/* 设置相关属性 */
export type HostConfig = {
  host: string;
  port: number;
  secure: boolean,
}

export type LoginInfo = {
  username: string;
  password: string
}

export type Properties = {
  // 发信相关属性
  // 服务器相关的属性，imap/pop3 只需要填一个，smtp必填
  imap?: HostConfig;
  pop3?: HostConfig;
  smtp?: HostConfig;

  oauth2?: string;
  // 用户账号信息
  userInfo: LoginInfo;
  // 其他属性
  extra?: {
    [prop: string]: string | boolean | number;
  };
  ca?: string[]
};

export interface ITransport {
  // 设置属性
  setProperties(properties: Properties): void;
  // 检查配置是否正确
  check(): Promise<void>;
  // 发送邮件
  sendMail(
    from: EmailAddress,
    toList: EmailAddress[],
    ccList: EmailAddress[],
    bccList: EmailAddress[],
    subject: string,
    richText: string,
    plainText: string,
    inlineImages: { content: IBuffer; fileName: string; contentId: string }[],
    attachments: { fileName: string; content: IBuffer}[],
    extraHeaders?: MimeHeader
  ): Promise<void>;
  // sendMime(mime: Mime): Promise<void>;
}

type QueryFieldKey =
  | "from"
  | "to"
  | "subject"
  | "cc"
  | "bcc"
  | "attachment"
  | "message-id"
  | "x-message-id"
  | "body";
type QueryField = {
  [key in QueryFieldKey]?: string;
};
export type Query = {
  fields: QueryField;
  folder: string;
  attrs?: MailAttribute[];
  text?: string;
};

export type QueryValue = string | {}
export type QueryOption = {

}

export type Filter = {
  isFlagged?: boolean,
  isSeen?: boolean,
  folderFullName?: string,
}

export enum StoreFeature {
  Folder, // 支持文件夹增删改操作
  VirtualFolder, // 支持虚拟文件夹，比如重要、未读等
  MailPart, // 支持分块下载邮件内容
  MailAttribute, // 支持设置邮件属性：已读、已标记等
  NeedToSync, // 需要同步，需要调用sync方法
  CustomFolderAndMail,  // 由IStore来生成IFolder和IMail对象
}

export const ORIGINAL_DELIMITER = "/";

export enum MailPartId {
  HTML = 1,
  PLAIN = 2,
  ATTACHMENT_BEGIN = 101,
  ATTACHMENT_END = 199,
  INLINE_BEGIN = 201,
  INLINE_END = 299,
}

export interface IStore {

  /**
   * 获取文件分隔符。
   * 特殊场景下，服务器返回的文件分隔符非/符号，为方便开发者使用，通过本方法进行动态转换
   * @param isForOriginal 使用场景是否为本地
   * @returns 返回适配场景的文件分隔符
   */
  getDelimiter(isForOriginal: boolean):string;

  // 设置属性
  setProperties(properties: Properties): void;
  // 检查配置是否正确
  check(): Promise<void>;
  // 获取支持的功能，参考StoreFeature的定义。目前主要是区分IMAP和POP3协议的功能
  supportedFeatures(): StoreFeature[];
  // 是否支持某个功能
  hasFeature(feature: StoreFeature): boolean;

  /*
   * 触发同步操作，同步操作会更新文件夹的邮件列表，更新邮件的属性等
   * 需要支持StoreFeature.NeedToSync才会被调用
   */
  sync?(): void;

  /*
   * 如果Store支持了CustomFolderAndMail特性，才需要实现以下接口
   */
  getRootFolder?(): Promise<IFolder>

  /*
   * 新邮件通知，当有新邮件到达时，会触发该事件
   * 对于IMAP协议，可以通过IDLE命令来实现，对于POP3协议，需要定时轮询
   * 对于IMAP协议，新邮件是指新加入到文件夹的邮件，有可能是已读的，也有可能是未读的
   * 回调函数的参数是文件夹夹名称和新邮件id列表
   */
  on(
    event: "new-mail",
    handler: (folderFullName: string, mailIdList: string[]) => void
  ): void;

  /*
   * 其他事件，看具体Store对象的实现
   */
  on(event: string, handler: Function): void;

  /*
   * 获取邮件全部内容，包括头部、正文、附件等，是一个MIME格式的字符串
   * @param folderFullName: 文件夹名称
   * @param mailId: 邮件id
   * @return: 邮件内容，是一个MIME格式的字符串，使用IBuffer存储
   * @deprecated 使用getMailRaw接口
   */

  getMail(folderFullName: string, mailId: string): Promise<IBuffer>;
  /*
   * 获取邮件全部内容，包括头部、正文、附件等，是一个MIME格式的字符串
   * @param folderFullName: 文件夹名称
   * @param mailId: 邮件id
   * @return: 邮件内容，是一个MIME格式的字符串，使用IBuffer存储
   */
  getMailRaw(folderFullName: string, mailId: string): Promise<IBuffer>;

  /* 获取邮件部分内容，对于multipart的邮件，可以指定获取某个部分的内容
   * partId是一个字符串，格式是 1.2.3，表示获取第1个子部分的第2个子部分的第3个子部分的内容
   * @param folderFullName: 文件夹名称
   * @param mailId: 邮件id
   * @param partId: 邮件的部分id
   * @return: 返回对应的part的内容，不包括头部，未解码。使用时需要根据MailStructure来解码
   */
  getMailPartContent(
    folderFullName: string,
    mailId: string,
    partId: string | MailPartId
  ): Promise<IBuffer>;

  /* 获取邮件的基本信息，包括信封、结构、属性等，参考MailBasic的定义
   * @param folderFullName: 文件夹名称
   * @param mailId: 邮件id
   */
  getMailBasic(folderFullName: string, mailId: string): Promise<MailBasic>;
  getMailSize(folderFullName: string, mailId: string): Promise<number>;

  /* 获取邮件的头信息，即邮件MIME的头信息
   * @param folderFullName: 文件夹名称
   * @param mailId: 邮件id
   * @return MimeHeader对象
   */
  getMailHeaders(folderFullName: string, mailId: string): Promise<MimeHeader>;

  /* 获取邮件的单个头信息，即邮件MIME的头信息
   * @param folderFullName: 文件夹名称
   * @param mailId: 邮件id
   * @param header: 邮件头的名字
   * @return 如果邮件头存在，则返回它的值，如果存在多个，则返回一个数组，如果不存在，则返回undefined
   */
  getMailHeader(folderFullName: string, mailId: string, header: string): Promise<string | string[] | undefined>;

  /*
   * 获取邮件结构
   */
  getMailStructure(
    folderFullName: string,
    mailId: string
  ): Promise<MailStructure>

  /*
   * 获取邮件各个part的基本信息
   */
  getMailParts(
    folderFullName: string,
    mailId: string
  ): Promise<MailPart[]>

  /*
   * 获取邮件属性
   */
  getMailAttributes(
    folderFullName: string,
    mailId: string
  ): Promise<MailAttribute[]>

  /*
   * 获取邮件信封数据
   */
  getMailEnvelope(
    folderFullName: string,
    mailId: string
  ): Promise<MailEnvelope>

  /*
   * 获取邮件的序号，序号是一个数字，从1开始
   * 如果mailId不是合法，或者找不到邮件，reject
   */
  getMailIndex(
    folderFullName: string,
    mailId: string,
    order?: Order
  ): Promise<number>;

  /* 设置邮件的属性，返回设置后的属性列表
   * @param folderFullName: 文件夹名称
   * @param mailId: 邮件id
   * @param attributes: 邮件属性列表
   * @param modifyType: "+"表示添加属性，"-"表示删除属性，""表示替换属性
   */
  setMailAttributes(
    folderFullName: string,
    mailId: string,
    attributes: MailAttribute[],
    modifyType: "+" | "-" | ""
  ): Promise<MailAttribute[]>;

  // 文件夹相关操作，需要支持StoreFeature.Folder才可以使用。
  // 标准协议里面，IMAP支持，POP3不支持

  // 创建子文件夹
  createSubFolder(folderName: string, parent: string): Promise<void>;
  // 重命名文件夹
  renameFolder(folderName: string, newName: string): Promise<void>;
  // 删除文件夹，
  deleteFolder(folderName: string): Promise<void>;
  // 获取全部文件夹列表
  getAllFolderList(): Promise<FolderData[]>;
  // 获取文件夹列表
  getFolderList(parent: string): Promise<FolderData[]>;
  // 获取文件夹信息，参考FolderData的定义
  getFolderInfo(folderName: string): Promise<FolderData>;
  // 打开文件夹，文件夹需要打开之后才可以读取邮件
  // 对应IMAP的select指令，POP3不需要打开
  openFolder(folderName: string): Promise<FolderData>;
  // 关闭文件夹，关闭之后就不能读取邮件了
  // 对应IMAP的close指令，POP3不需要关闭
  closeFolder(folderName: string): Promise<void>;
  // 判断文件夹是否打开
  isFolderOpen(folderName: string): boolean;

  /*
   * 获取文件夹指定范围的邮件id列表
   * @param folderName: 文件夹名称
   * @param start: 开始的邮件序号，从1开始
   * @param size: 获取邮件的数量
   * @param order: 排序规则，参看Order的定义
   * return: 邮件id列表
   */
  getFolderMailIdList(
    folderName: string,
    start: number,
    size: number,
    order?: Order
  ): Promise<string[]>;

  /*
  * 移动一封邮件到目标文件夹
  * 返回邮件在新文件夹的id
   */
  moveMail(
    fromFolderFullName: string,
    mailId: string,
    toFolderFullName: string
  ): Promise<string>;

  /*
  * 复制一封邮件到目标文件夹
  * 返回邮件在新文件夹的id
   */
  copyMail(
    fromFolderFullName: string,
    mailId: string,
    toFolderFullName: string
  ): Promise<string>;

  /*
  * 往文件夹里添加邮件
  * 返回邮件id
   */
  addMail(mail: string | IBuffer, folderName: string, attrs?: MailAttribute[]): Promise<string>;

  /*
  * 删除邮件
   */
  deleteMail(fromFolder: string, mailId: string): Promise<void>;

  /*
   * deprecated, use queryMail
   */
  searchMail(query: Query, orderBy?: Order): Promise<Map<string, string[]>>;

  /*
   * 搜索邮件
   */
  queryMail?(query: Query): Promise<[string, MailBasic][]>;

  /*
  * 断开连接，释放资源
   */
  release?(): void;
  /*
   * 设置缓存生成器。协议内部需要的IBuffer对象，由这个生成器来构造。如果没传，则使用内存对象。
   */
  setBufferCreator(creator: IBufferCreator): void;
}

export enum ErrorCode {
  Error = 1,
  // 未知错误
  UNKNOWN_ERROR = 1,
  // 协议解析出错，一般是格式问题
  PROTOCOL_ERROR,
  // 连接服务器超时
  CONNECT_TIMEOUT,
  // 登录失败
  LOGIN_FAILED,
  // 连接不到服务器
  CONNECT_FAILED,
  // 发送数据失败
  SEND_DATA_FAILED,

  // 参数错误
  PARAMETER_ERROR,
  // 参数缺失
  PARAMETER_MISSED,
  // 找不到文件夹
  FOLDER_NOT_FOUND,
  FOLDER_EXISTS,
  // 创建文件夹失败
  FOLDER_CREATE_FAILED,
  // 重命名文件夹失败
  FOLDER_RENAME_FAILED,
  // 删除文件夹失败
  FOLDER_DELETE_FAILED,
  // 获取文件夹列表失败
  FOLDER_LIST_FAILED,

  // 解析邮件内容出错
  PARSE_MAIL_FAILED,
  // 解析MIME格式出错
  PARSE_MIME_FAILED,
  // 解析RTF出错
  PARSE_RTF_FAILED,
  // 不支持
  NOT_SUPPORTED,
  // 方法未实现
  NOT_IMPLEMENTED,
  // 找不到附件
  ATTACHMENT_NOT_FOUND,
  // 找不到内联图片
  INLINE_IMAGE_NOT_FOUND,

  DELETE_MAIL_FAILED,

  SENT_MAIL_FAILED,

  MAIL_NOT_FOUND,

  // 获取邮件失败
  GET_MAIL_FAILED,

  NO_AVAILABLE_NETWORK,

  SERVER_UN_REACHABLE,
  SERVER_NOT_SUPPORT,
  CREATE_FOLDER_FAILED,
  MAIL_PART_NOT_FOUND,
  MAIL_ATTRIBUTE_NOT_FOUND,
  MAIL_ATTRIBUTE_NOT_SUPPORT,
  MAIL_ATTRIBUTE_NOT_WRITABLE,
  ADD_MAIL_FAILED,

  // RTF解析参数超出限制
  RTF_ARGMENT_OUT_OF_RANGE,
  // RTF解析失败（一般是未能支持的参数）
  RTF_PARSER_FAILED,

  IMAP_UNEXPECTED_END_OF_LINE,
  IMAP_PARSER_FAILED,

  // OAuth2错误
  OAUTH2_REQUEST_TOKEN_FAILED,

  // 协议相关错误
  // 前一个协议命令还没执行完，就发起新的协议命令
  PROTOCOL_PARALLEL_COMMAND,
  // 执行协议命令出错
  PROTOCOL_EXECUTE_COMMAND_FAILED,
  // 协议超时：超过一定时间没有数据返回
  // 对于下载大邮件等场景，需要时间较长的，不会触发此错误
  PROTOCOL_TIMEOUT,
}
export class CMError extends Error {
  code: ErrorCode;
  extra?: Object;
  constructor(message: string, code: ErrorCode = ErrorCode.Error, extra?: Object) {
    super(message);
    this.code = code;
    this.extra = extra;
  }
}
